package cn.zhaozhiqi.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.zhaozhiqi.constants.Constants;
import cn.zhaozhiqi.constants.ErrorCode;
import cn.zhaozhiqi.exception.GlobalException;
import cn.zhaozhiqi.mapper.DriverMapper;
import cn.zhaozhiqi.pojo.domain.Driver;
import cn.zhaozhiqi.pojo.domain.DriverSetting;
import cn.zhaozhiqi.pojo.domain.DriverSummary;
import cn.zhaozhiqi.pojo.driver.dto.DriverSettingDto;
import cn.zhaozhiqi.pojo.dto.DriverGeoDto;
import cn.zhaozhiqi.pojo.dto.DriverRegisterDto;
import cn.zhaozhiqi.pojo.order.dto.OrderCreateMQDto;
import cn.zhaozhiqi.pojo.vo.DriverWorkbenchVo;
import cn.zhaozhiqi.pojo.vo.WXRegisterVo;
import cn.zhaozhiqi.remote.api.ILoginFeignClient;
import cn.zhaozhiqi.remote.dto.LoginDto;
import cn.zhaozhiqi.service.IDriverService;
import cn.zhaozhiqi.service.IDriverSettingService;
import cn.zhaozhiqi.service.IDriverSummaryService;
import cn.zhaozhiqi.service.IDriverWalletService;
import cn.zhaozhiqi.template.WeChatTemplate;
import cn.zhaozhiqi.utils.AssertUtil;
import cn.zhaozhiqi.utils.BitStatesUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


/**
 * <p>
 * 司机对象 服务实现类
 * </p>
 *
 * @author zhaozhiqi
 * @since 2024-12-17
 */
@Service
@Slf4j
public class DriverServiceImpl extends ServiceImpl<DriverMapper, Driver> implements IDriverService {

    @Autowired
    private IDriverSettingService driverSettingService;
    @Autowired
    private IDriverSummaryService summaryService;
    @Autowired
    private IDriverWalletService walletService;
    @Autowired
    private WeChatTemplate weChatTemplate;
    @Autowired
    private ILoginFeignClient feignClient;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DefaultMQPullConsumer pullConsumer;

    /**
     * 拿到rocketMQ中的订单的方法
     *
     * @return
     */
    @Override
    public List<OrderCreateMQDto> getOrderById() {
        //司机的ID
        Long loginId = Long.valueOf(StpUtil.getLoginId().toString());

        try {
            //订阅消息队列，指定topic
            //Constants.MQ.TOPIC_ORDER+":"+String.format(Constants.MQ.TAGS_ORDER_DRIVER,driverId);
            Set<MessageQueue> messageQueues = pullConsumer.fetchSubscribeMessageQueues(Constants.Mq.ORDER_TOPIC);

            //封装结果
            List<OrderCreateMQDto> result = new ArrayList<>();

            for (MessageQueue messageQueue : messageQueues) {

                //通过偏移量来获取消息 ：当前队列的消费位置
                long offset = pullConsumer.fetchConsumeOffset(messageQueue, true);

                //拉取消息，需要指定tags拼接司机ID， 来区分司机:
                String tags = String.format(Constants.Mq.ORDER_LAG_NAME, loginId);
                PullResult pullResult = pullConsumer.pull(messageQueue, tags, offset, 5);

                //找到消息
                if (pullResult != null && pullResult.getPullStatus().equals(PullStatus.FOUND)) {
                    //拿到消息列表
                    List<MessageExt> messageExtList = pullResult.getMsgFoundList();

                    if (messageExtList == null || messageExtList.size() == 0) {continue;}

                    for (MessageExt messageExt : messageExtList) {
                        //拿到消息内容，转换为订单对象
                        String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
                        OrderCreateMQDto orderMesssage = JSON.parseObject(message, OrderCreateMQDto.class);

                        //添加到结果列表
                        result.add(orderMesssage);
                        log.info("获取订单消息 {}", orderMesssage);
                    }
                }

                if (pullResult != null) {
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    pullConsumer.updateConsumeOffset(messageQueue, pullResult.getNextBeginOffset());
                }

            }
            return result;
        } catch (Exception e) {
            throw new GlobalException("订单拉取失败");
        }

    }

    /**
     * 缓存司机的geo坐标的方法
     *
     * @param dto
     * @return
     */
    @Override
    public Boolean saveGeo(DriverGeoDto dto) {
        long driverId = StpUtil.getLoginIdAsLong();
        Point point = new Point(dto.getLongitude(), dto.getLatitude());
        redisTemplate.opsForGeo().add(Constants.Redis.DRIVER_GEO_KEY, point, driverId);
        return true;
    }


    /**
     * 司机开始接单的方法
     *
     * @return
     */
    @Override
    public Boolean online() {
        //开始接单状态不需要修改数据库，仅将相关信息存储在redis内即可证明状态为接单
        long driverId = StpUtil.getLoginIdAsLong();
        DriverSetting setting = driverSettingService.getById(driverId);
        DriverSettingDto dto = new DriverSettingDto();
        BeanUtils.copyProperties(setting, dto);
        //将相关数据存储到redis中
        String key = Constants.Redis.DRIVER_SETTING + driverId;
        redisTemplate.opsForValue().set(key, dto);
        return true;
    }

    /**
     * 司机停止接单方法
     *
     * @return
     */
    @Override
    public Boolean offline() {
        //司机停止接单，1.删除redis中的相关setting信息；
        long driverId = StpUtil.getLoginIdAsLong();
        String key = Constants.Redis.DRIVER_SETTING + driverId;
        redisTemplate.delete(key);
        //2.删除redis中的GEO坐标
        redisTemplate.opsForGeo().remove(Constants.Redis.DRIVER_GEO_KEY, driverId);
        return true;
    }


    /**
     * 返回工作台的相关信息
     *
     * @return
     */
    @Override
    public DriverWorkbenchVo getWorkbench() {
        DriverWorkbenchVo vo = new DriverWorkbenchVo();
        //通过登录id拿到当前的登录人信息
        long DriverId = StpUtil.getLoginIdAsLong();
        Driver driver = super.getById(DriverId);
        //根据司机的位状态判断其是否通过实名认证
        vo.setRealAuthStatus(BitStatesUtils.hasState(driver.getBitState(), BitStatesUtils.OP_REAL_AUTHENTICATIONED));
        DriverSummary summery = summaryService.getById(DriverId);
        AssertUtil.isNotNull(summery, "基本信息不能为空");
        BeanUtils.copyProperties(summery, vo);
        return vo;
    }


    /**
     * 司机注册的方法，注册时将关联表以及login表等一对一的表均配置上
     *
     * @param dto
     * @return
     */
    @Override
    public Boolean driverRegister(DriverRegisterDto dto) {
        //拿到司机的手机号
        String phoneNum = "";
        if (StrUtil.isNotEmpty(dto.getPhoneCode())) {
            phoneNum = weChatTemplate.getPhoneNum(dto.getPhoneCode());
        }
        //封装了一个start的公共依赖
        WXRegisterVo wxRegisterVo = weChatTemplate.getOpenid(dto.getCode());
        //拿到openid我们需要先判断是否数据库中有这个数据，这就需要重新写一个专属mapper。这样我们可以用mybatisplus中的工具类生成
        LambdaQueryWrapper<Driver> wapper = new LambdaQueryWrapper<Driver>().eq(Driver::getOpenId, wxRegisterVo.getOpenid());
        //通过ServiceImpl内置的方法为我们寻找是否有满足要求的Driver
        Driver driver = super.getOne(wapper);
        //判断拿到的driver是否为空，如果不为空的话就直接抛出异常,使用hutool工具包
        AssertUtil.isNull(driver, ErrorCode.PARAM_DRIVER_EXIST);
        /*如果为空，就进行新增
        我们需要观察哪些表需要同时进行新增？一般情况下是一对一关系的表均需要进行新增操作
         */
        //根据hutool内的id工具类的雪花算法生成id
        Snowflake snowflakeID = IdUtil.createSnowflake(2, 4);
        long DriverId = snowflakeID.nextId();

        //保存司机
        createDriver(wxRegisterVo, DriverId, phoneNum);
        //保存司机注册的关联表
        driverSettingService.create(DriverId);
        summaryService.create(DriverId);
        walletService.create(DriverId);

        //保存司机的登录表
        LoginDto loginDto = new LoginDto();
        loginDto.setId(DriverId);
        loginDto.setType(Constants.Driver.TYPE_DRIVER);
        loginDto.setOpenId(wxRegisterVo.getOpenid());
        loginDto.setPhone(phoneNum);
        feignClient.register(loginDto);
        return true;
    }


    //保存司机的方法
    private void createDriver(WXRegisterVo wxRegisterVo, long DriverId, String phoneNum) {
        Driver driver;
        //因为是空，所以就拿出来操作
        driver = new Driver();
        //进行司机表的保存
        driver.setId(DriverId);
        driver.setCreateTime(new Date());
        driver.setOpenId(wxRegisterVo.getOpenid());
        driver.setBitState(1L);
        super.save(driver);
    }
}
